/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package Server;
import Common.Message;
import java.io.*;
import java.net.*;
import java.util.ArrayList;

/**
 * A ServerThread is created by the server (main class of the server application)
 * for each client connected. It manage the communication between server and client application.
 * 
 * @author hartingm
 */
public class ServerThread
		extends Thread{

    /********************
     * Attributes
     ********************/
	private Socket transfertSocket;
	private Server server;
	private String nickname; //client nickname
	private boolean connected;
    private int kickVar;
	private ArrayList <String> kickList;

	/**
	 * Create a new ServerThread
	 * @param name the thread name
	 * @param server the main server which created the thread
	 * @param transfertSocket the socket created by main server
	 */
	public ServerThread(String name, Server server, Socket transfertSocket) {
		super(name);
		this.server = server;
        nickname = null;
		this.transfertSocket = transfertSocket;
		connected = false;
		this.kickList = new ArrayList<String>();
		this.start();
	}

	/**
	 * The method executed by the thread.
	 */
	public void run() {
		for(;;) {
            Message message = readMessage();
			if (message != null)
				messageTreatment(message);
			else {
				System.out.println(this.getName()+": Error while receiving message.");
				closeConnection();
			}
		}
	}//end run
	
	/**
	 * Return the client nickname.
	 * @return client nickname
	 */
	public String getNickname() {
		return nickname;
	}


	/**
     * return the value of the kickVar attribute.
	 * @return value of the kickVar attribute
	 */
    public int getKickVar() {
		return kickVar;
    }// end  getvarkick

	/**
	 * Add one to the current value of kickVar.
	 */
	private synchronized void incrementKickVar() {
		this.kickVar += 1;
	}//end incrementKickVar

	/**
	 * The method return true if the nickname is already present
	 * in the kickList.
	 * @param nickname the nickname to look for
	 * @return true if the nickname is already present
	 */
	private synchronized boolean isInKickList(String nick) {
		return kickList.contains(nick);
	}//end isInKickList

	/**
	 * Remove clients who are present in the kickList and now disconnected from server.
	 */
	private synchronized void cleanKickList(){
		for(int i=0; i<kickList.size(); i++){
			if (server.getClient(kickList.get(i)) == null) { //the client in the kickList is now disconnected
				kickList.remove(i);
			}
		}
	}//end cleanKickList

	/**
	 * Add a nickname to the kickList
	 * @param nick the nickname to add into the kickList
	 */
	private synchronized void addToKickList(String nick) {
		kickList.add(nick);
	}//end addToKickList


	/**
	 * If the client is already in the
	 * kickList the the method send a message to warning him that
	 * he can't kick the same client twice.
	 * Else the method increments the kickVar and add the nickname in
	 * the kickList. The method send a message to everybody to warning
	 * that a client has benn kicked. If there is client who kicked the client
	 * and they are now disconnected the method delete them from the kickList.
	 * If the value of the kickVar equals the number of connected clients plus one
	 * the client is disconneced from server.
	 * 
	 * @param nick the nickname who send the kick message
	 */
	public void kick(String nick) {
		if(isInKickList(nick)) {
			server.getClient(nick).sendMessage(new Message(Message.PUBLIC_MESSAGE, "Server", nick, "You have already kicked this guy!")); //send a warning message
			cleanKickList();
		}
		else {
			incrementKickVar();
			addToKickList(nick);
			cleanKickList();
			System.out.println(this.getName()+": new kickVar value = "+getKickVar());
			sendToEverybody(Message.PUBLIC_MESSAGE, nick + " wants to kick me !"); //warning everybody
			sendMessage(new Message(Message.PUBLIC_MESSAGE, "Server", nick, nick + " wants to kick you !")); //warning the client who have just been kicked
			if (getKickVar() > ((server.countClient()/2))) { //this client received too much kick messages
				System.out.println(this.getName()+": too much kick messages received ("+getKickVar()+") -> KICKED");
				server.removeClient(this);
				System.out.println(this.getName()+": Sending update messages to everybody.");
                sendToEverybody(Message.DELETE_CLIENT, "none");
				sendMessage(new Message(Message.KICK_MESSAGE, nick, this.nickname, "none"));
				closeConnection();
			}
		}
		
	}//end kick
	
    /**
	 * Read a message from inputData.
	 * @return the received message, null if an error has occured
	 */
        public Message readMessage() {
            try {
                InputStream inputStream = transfertSocket.getInputStream();
                DataInputStream dataInputStream = new DataInputStream(inputStream);

				byte[] messageByte = new byte[1024];
				String messageString;
				Message mess;

                byte type = dataInputStream.readByte();
                String nickSrc = dataInputStream.readUTF();
                String nickDest = dataInputStream.readUTF();
                
				//the message can be a byte[] or a string
				if (type == Message.FILE) {
					int l = dataInputStream.readInt();
					dataInputStream.read(messageByte);
					mess = new Message(type, nickSrc, nickDest, l, messageByte);
				}
				else {
					messageString = dataInputStream.readUTF();
					mess = new Message(type, nickSrc, nickDest, messageString);
				}

				System.out.println(this.getName() + ": Message received.");
                return mess;
            }
            catch (Exception e){
				if (e instanceof EOFException) {
						System.out.println(this.getName()+": ERROR connection lost ("+e.toString()+").");
						System.out.println(this.getName()+": Removing client from list.");
						if (server.removeClient(this) == true) {
							System.out.println(this.getName()+": Sending update messages to everybody.");
							sendToEverybody(Message.DELETE_CLIENT, "none"); //send update message to everybody
							closeConnection();
						}
						else {
							System.out.println(this.getName()+": Sending disconnection message to client.");
							Message disconnectionMessage = new Message(Message.DISCONNECTION, this.getName(), nickname, "ERROR");
							sendMessage(disconnectionMessage);

							System.out.println(this.getName()+": Closing thread client not connected.");
							closeConnection();
						}
					}
				else
					System.out.println(this.getName() + ": ERROR class ServerThread method readMessage"+e.toString());
                return null;
            } 
        }//end readMessage

		/**
		 * Send a message to client.
		 * @param message the message to send
		 */
        public void sendMessage(Message message) {
            try {
		        OutputStream outputStream = transfertSocket.getOutputStream();
                DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
                    
                dataOutputStream.write(message.getType());
                dataOutputStream.writeUTF(message.getNickSrc());
                dataOutputStream.writeUTF(message.getNickDest());
				if(message.getType()==Message.FILE) {
					dataOutputStream.writeInt(message.getLength());
					dataOutputStream.write(message.getMessageBytes());
				} else {
					dataOutputStream.writeUTF(message.getMessage());
				}
				

				System.out.println(this.getName() + ": Message sent.");
			}
                catch(Exception e){
					if (e instanceof EOFException) {
						System.out.println(this.getName()+": ERROR connection lost ("+e.toString()+").");
						System.out.println(this.getName()+": Removing client from list.");
						if (server.removeClient(this) == true) {
							System.out.println(this.getName()+": Sending update messages to everybody.");
							sendToEverybody(Message.DELETE_CLIENT, "none"); //send update message to everybody
							closeConnection();
						}
						else {
							System.out.println(this.getName()+": Sending disconnection message to client.");
							Message disconnectionMessage = new Message(Message.DISCONNECTION, this.getName(), nickname, "ERROR");
							sendMessage(disconnectionMessage);

							System.out.println(this.getName()+": Closing thread client not connected.");
							closeConnection();
						}
					}
					else
						System.out.println(this.getName() + ": ERROR class ServerThread method sendMessage"+e.toString());
                }
        }//end sendMessage

		/**
		 * Send a message to every connected client.
		 * @param type the type of message to send
		 * @param mess the message of message to send
		 */
        public void sendToEverybody(byte type, String mess){
			//send a message to every connected client except sender
			
			ArrayList<ServerThread> clientList = server.getClientList();
			
			for(int i=0; i<clientList.size(); i++) {
				ServerThread client = clientList.get(i);
				
				if(client != this){ //if client is not the sender
					Message message = new Message(type, nickname, client.getNickname(), mess);
					client.sendMessage(message);
				}
			}
		}//end sendToEverybody

		/**
		 * Send addClient messages for every client in connected list.
		 */
		public void sendConnectedList() {
			//send the complereturn te list of connected client (except client connected to this thread) to nickDest
			
			ArrayList<ServerThread> clientList = server.getClientList();
			String empty = "none";

			for(int i=0; i<clientList.size(); i++) { //if client is not the client connected to this thread
				ServerThread client = clientList.get(i);
				if (client != this) {
					Message message = new Message(Message.ADD_CLIENT, client.getNickname(), this.getNickname(), empty);
					sendMessage(message);
				}
			}
		}//end sendConnectedList

    /**
	 * Analyse the message and do the action associated by it's type.
	 * @param message the message to analyse
	 */
        private void messageTreatment(Message message) {
            
            switch(message.getType()) {
                //connection message
                case Message.CONNECTION:
                    nickname = message.getNickSrc();
                    if (server.addClient(this) == true) {						
						connected = true;
                        Message ok = new Message(Message.CONNECTION_OK, "none", nickname, "Connection ok.");
                        sendMessage(ok);
						System.out.println(this.getName()+": Sending update messages to everybody.");
                        sendToEverybody(Message.ADD_CLIENT, "none"); //send update message to everybody
						System.out.println(this.getName()+": Sending connected list to new client.");
                        sendConnectedList();	//send connected list to new client

						System.out.println(this.getName()+": "+this.getNickname()+" is now connected!");
                    }
                    else {
						System.out.println(this.getName()+": ERROR: client already connected.");
						
						System.out.println(this.getName()+": Sending disconnection message to client.");
						Message disconnectionMessage = new Message(Message.DISCONNECTION, this.getName(), nickname, "Nickname already in use.");
						sendMessage(disconnectionMessage);
						closeConnection();
                    }
                    break;
                    
                //diconnection message
                case Message.DISCONNECTION:
                    if (!connected)
                        System.out.println(this.getName() + ": Client not connected, action denied");
                    else {
                        System.out.println(this.getName()+": Removing client from list.");
                        if (server.removeClient(this) == true) {
                            System.out.println(this.getName()+": Sending update messages to everybody.");
                            sendToEverybody(Message.DELETE_CLIENT, "none"); //send update message to everybody
                            closeConnection();
                        }
                        else {
                            System.out.println(this.getName()+": Sending disconnection message to client.");
                            Message disconnectionMessage = new Message(Message.DISCONNECTION, this.getName(), nickname, "Nickname already in use.");
                            sendMessage(disconnectionMessage);

                            System.out.println(this.getName()+": Closing thread client not connected.");
                            closeConnection();
                        }
                    }
                    break;
                    
                //message to one client
				//file propose
				//file accept
				//file deny
				//piece of file
				//file end
				case Message.PRIVATE_MESSAGE:
				case Message.FILE_PROPOSE:
				case Message.FILE_ACCEPT:
				case Message.FILE_DENY:
				case Message.FILE:
				case Message.FILE_END:
                    if (!connected)
                        System.out.println(this.getName() + ": Client not connected, action denied");
                    else {
                        ServerThread client = server.getClient(message.getNickDest());
                        if(client != null) //client nickDest is connected
                            client.sendMessage(message);
                        else //client NickDest not connected
                            System.out.println(this.getName()+": Impossible to send message: destination client not connected.");
                            //-send update message to Src client ????
                    }
                    break;

                //message to everybody
                case Message.PUBLIC_MESSAGE:
                    if (!connected)
                        System.out.println(this.getName() + ": Client not connected, action denied");
                    else {
                        sendToEverybody(Message.PUBLIC_MESSAGE, message.getMessage());
                        System.out.println(this.getName() + ": Message sent to everybody.");
                    }
                    break;
                    
                //kick message
                case Message.KICK_MESSAGE:
                    if (!connected)
                        System.out.println(this.getName() + ": Client not connected, action denied");
                    else {
						ServerThread clientKicked = server.getClient(message.getNickDest());
						if (clientKicked != null) {
							clientKicked.kick(nickname);
						}
                    }
					break;

                //not recognized type
                default:
					System.out.println(this.getName()+": Not recognized message type("+message.getType()+") -> ERROR.");
                break;
            }
        }//end messageTreatment

		
		/**
		 * Close the connection with client. Close the socket and stop the thread.
		 */
		private void closeConnection() {
			//close transfertSocket and interrupt ServerThread
			try {
				Thread.sleep(2000);
				System.out.println(this.getName()+": Closing transfertSocket.");
				transfertSocket.close();
				System.out.println(this.getName()+": STOPPED.");
				this.stop();
			}
			catch(Exception e) {
				System.out.println(this.getName()+": ERROR class ServerThread method closeConnection ("+e.toString()+").");
			}
		}//end closeConnection

}//end ServerThread class
